package ca.abubakrsagar.breadboardsim;

import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

/**
 * A Mouse and MouseMotion listener used to allow the user to drag components
 * onto the breadboard.
 * 
 * @author Abubakr
 * @since Wednesday, June 13, 2012
 * 
 */
public class DragController implements MouseListener, MouseMotionListener {

	private BreadboardPanel brPanel; // The Breadboard Panel upon which the controller
								// functions.
	private Point parentPos, anchorPos; // Points used to offset the location of
								// components to allow the user the ability to
								// drag.
	private Component source; // Stores the object that the mouse was pressed upon in
						// order to locate that specific object from all the
						// objects on the breadboard.
	private int[][] componentTrack = new int[1][2]; // An array of integer values used
											// to keep
											// track of the type and index of
											// component that was clicked.

	/**
	 * Creates a new drag controller which associates this class' breadboard
	 * panel with the given breadboard panel.
	 * 
	 * @param brPanel
	 *            The breadboard panel object.
	 */
	public DragController(BreadboardPanel brPanel) {
		this.brPanel = brPanel;
	}

	/**
	 * Updates the position of the object being dragged according to the parent
	 * position and anchor position. Also brings this object to the front to
	 * ensure that it was easily visible and not being overlapped.
	 */
	public void mouseDragged(MouseEvent e) {
		// Based on which component is to be moved, update its bounds with
		// respect to the mouse on the screen, parent position and anchor
		// position.
		if (componentTrack[0][0] == 0) {
			Point mouseOnScreen = e.getLocationOnScreen();
			brPanel.getBreadboard()
					.getLED()
					.get(componentTrack[0][1])
					.setBounds(mouseOnScreen.x - parentPos.x - anchorPos.x,
							mouseOnScreen.y - parentPos.y - anchorPos.y, 24, 20);
			brPanel.moveToFront(brPanel.getBreadboard().getLED()
					.get(componentTrack[0][1]));
		} else if (componentTrack[0][0] == 1) {
			Point mouseOnScreen = e.getLocationOnScreen();
			brPanel.getBreadboard()
					.getResistor()
					.get(componentTrack[0][1])
					.setBounds(mouseOnScreen.x - parentPos.x - anchorPos.x,
							mouseOnScreen.y - parentPos.y - anchorPos.y, 27, 37);
			brPanel.moveToFront(brPanel.getBreadboard().getResistor()
					.get(componentTrack[0][1]));
		} else if (componentTrack[0][0] == 2) {
			Point mouseOnScreen = e.getLocationOnScreen();
			brPanel.getBreadboard()
					.getSwitch()
					.get(componentTrack[0][1])
					.setBounds(mouseOnScreen.x - parentPos.x - anchorPos.x,
							mouseOnScreen.y - parentPos.y - anchorPos.y, 27, 27);
			brPanel.moveToFront(brPanel.getBreadboard().getSwitch()
					.get(componentTrack[0][1]));
		}
		// Update the breadboard panel to display the new positions.
		brPanel.repaint();
	}

	/**
	 * Setups the parent and anchor positions and source upon the mouse being
	 * pressed. Locates the selected component out of all the components on the
	 * breadboard and updates the componentTrack values.
	 */
	public void mousePressed(MouseEvent e) {

		// Setup the points to ensure the component is dragged around correctly.
		parentPos = brPanel.getLocationOnScreen();
		anchorPos = e.getPoint();

		// Using the object that the mouse was pressed upon, cast it as a
		// Component and set source equal to it. This can be safely casted
		// because the breadboard will only have components on it.
		source = (Component) e.getSource();
		componentTrack[0][0] = source.getComponentType();

		// Based on the type of component pressed, loop through all existing
		// components of that type until a match is found. Update componentTrack
		// to remember which index stores that specific component.
		if (componentTrack[0][0] == 0) {
			for (int i = 0; i < brPanel.getBreadboard().getLED().size(); i++) {
				if (source == brPanel.getBreadboard().getLED().get(i)) {
					brPanel.getBreadboard().getLED().get(i).setInToolBox(false);
					componentTrack[0][1] = i;
					break;
				}
			}
		} else if (componentTrack[0][0] == 1) {
			for (int i = 0; i < brPanel.getBreadboard().getResistor().size(); i++) {
				if (source == brPanel.getBreadboard().getResistor().get(i)) {
					brPanel.getBreadboard().getResistor().get(i)
							.setInToolBox(false);
					componentTrack[0][1] = i;
					break;
				}
			}
		} else if (componentTrack[0][0] == 2) {
			for (int i = 0; i < brPanel.getBreadboard().getSwitch().size(); i++) {
				if (source == brPanel.getBreadboard().getSwitch().get(i)) {
					brPanel.getBreadboard().getSwitch().get(i)
							.setInToolBox(false);
					componentTrack[0][1] = i;
					break;
				}
			}
		}

	}

	/**
	 * Upon release of the mouse, the component initially being dragged snaps to
	 * the closest grid point. The component map is also updated in order to
	 * record the locations of the components.
	 */
	public void mouseReleased(MouseEvent e) {
		int x, y;
		Point tempLoc;
		// Depending on what component was being dragged, snap to different
		// locations.
		if (componentTrack[0][0] == 0) {
			tempLoc = new Point(
					(brPanel.getBreadboard().getLED().get(componentTrack[0][1])
							.getLocationOnScreen().x - parentPos.x) - 30,
					(brPanel.getBreadboard().getLED().get(componentTrack[0][1])
							.getLocationOnScreen().y - parentPos.y) - 93);
			// Restricts location to within breadboard.
			if (tempLoc.x < 0) {
				tempLoc.x = 0;
			} else if (tempLoc.x > 637) {
				tempLoc.x = 637;
			}
			if (tempLoc.y < 0) {
				tempLoc.y = 0;
			} else if (tempLoc.y > 287) {
				tempLoc.y = 287;
			}

			// Calculate the x and y location to snap to.
			x = (int) tempLoc.x - (tempLoc.x % 10) + 32;
			y = (int) tempLoc.y - (tempLoc.y % 20) + 100;
			if (tempLoc.y < 42) {
				y += 0;
			} else if (tempLoc.y < 256) {
				tempLoc.y -= 50;
				y = (int) tempLoc.y - (tempLoc.y % 20) + 150;
			} else {
				y += 0;
			}

			// Update the location of the LED.
			brPanel.getBreadboard().getLED().get(componentTrack[0][1])
					.setBounds(x, y, 24, 20);

			// Calculate for the y and x values in the component map.
			if (y == 100 || y == 120 || y == 360 || y == 380) {
				y = (y - 100) / 20;
			} else {
				y = ((y - 150) / 20) + 2;
			}
			x = (x - 32) / 10;

			// Update the values in the component map to match the component
			// positions on the breadboard.
			brPanel.getBreadboard().setComponent(y, x,
					brPanel.getBreadboard().getLED().get(componentTrack[0][1]));
			if (x < 63) {
				brPanel.getBreadboard().setComponent(
						y,
						x + 1,
						brPanel.getBreadboard().getLED()
								.get(componentTrack[0][1]));
			}
			// If the component was previously on the map, remove its old entry.
			if (brPanel.getBreadboard().getLED().get(componentTrack[0][1])
					.getPosition()[0][0] != -1) {
				brPanel.getBreadboard().setComponent(
						brPanel.getBreadboard().getLED()
								.get(componentTrack[0][1]).getPosition()[0][1],
						brPanel.getBreadboard().getLED()
								.get(componentTrack[0][1]).getPosition()[0][0],
						null);
				brPanel.getBreadboard()
						.setComponent(
								brPanel.getBreadboard().getLED()
										.get(componentTrack[0][1])
										.getPosition()[0][1] + 1,
								brPanel.getBreadboard().getLED()
										.get(componentTrack[0][1])
										.getPosition()[0][0], null);
			}

			// Update the location of the component with its position value.
			brPanel.getBreadboard().getLED().get(componentTrack[0][1])
					.setPosition(0, 0, x);
			brPanel.getBreadboard().getLED().get(componentTrack[0][1])
					.setPosition(0, 1, y);

		} else if (componentTrack[0][0] == 1) {
			tempLoc = new Point(
					(brPanel.getBreadboard().getResistor()
							.get(componentTrack[0][1]).getLocationOnScreen().x - parentPos.x) - 30,
					(brPanel.getBreadboard().getResistor()
							.get(componentTrack[0][1]).getLocationOnScreen().y - parentPos.y) - 97);

			// Restricts location to within breadboard.
			if (tempLoc.x < 0) {
				tempLoc.x = 0;
			} else if (tempLoc.x > 637) {
				tempLoc.x = 637;
			}
			if (tempLoc.y < 0) {
				tempLoc.y = 0;
			} else if (tempLoc.y > 287) {
				tempLoc.y = 287;
			}

			// Snap to closest grid point.
			x = (int) tempLoc.x - (tempLoc.x % 10) + 37;
			y = (int) tempLoc.y - (tempLoc.y % 20) + 100;

			// Offset y or location of resistor based on its location.
			// Necessary because the breadboard is not a consistent grid.
			if (tempLoc.y < 42) {
				y += 7;
			} else if (tempLoc.y < 256) {
				tempLoc.y -= 57;
				y = (int) tempLoc.y - (tempLoc.y % 20) + 157;
			} else {
				y += 7;
			}

			// Update bounds of the resistor based on calculated x and y values.
			brPanel.getBreadboard().getResistor().get(componentTrack[0][1])
					.setBounds(x, y, 27, 37);

			// Update x and y to be used in the array map for components.
			x = (x - 32) / 10;
			if (y == 100 || y == 120 || y == 360 || y == 380) {
				y = (y - 100) / 20;
			} else {
				y = ((y - 150) / 20) + 2;
			}

			// Add component's location to the component map.
			if (brPanel.getBreadboard().getResistor().get(componentTrack[0][1])
					.getDirection() == 1) { // If the resistor is vertical.
				brPanel.getBreadboard().setComponent(
						y,
						x,
						brPanel.getBreadboard().getResistor()
								.get(componentTrack[0][1]));
				brPanel.getBreadboard().setComponent(
						y + 1,
						x,
						brPanel.getBreadboard().getResistor()
								.get(componentTrack[0][1]));
				brPanel.getBreadboard().setComponent(
						y + 2,
						x,
						brPanel.getBreadboard().getResistor()
								.get(componentTrack[0][1]));
				// Remove the old entry on the componentMap if it exists.
				if (brPanel.getBreadboard().getResistor()
						.get(componentTrack[0][1]).getPosition()[0][0] != -1) {
					brPanel.getBreadboard()
							.setComponent(
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][1],
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][0], null);
					brPanel.getBreadboard()
							.setComponent(
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][1] + 1,
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][0], null);
					brPanel.getBreadboard()
							.setComponent(
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][1] + 2,
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][0], null);
				}
			} else {
				brPanel.getBreadboard().setComponent(
						y,
						x,
						brPanel.getBreadboard().getResistor()
								.get(componentTrack[0][1]));
				if (x < 63) {
					brPanel.getBreadboard().setComponent(
							y,
							x + 1,
							brPanel.getBreadboard().getResistor()
									.get(componentTrack[0][1]));
				}
				if (x < 62) {
					brPanel.getBreadboard().setComponent(
							y,
							x + 2,
							brPanel.getBreadboard().getResistor()
									.get(componentTrack[0][1]));
				}
				// If the component already exists on the component map, remove
				// the old location.
				if (brPanel.getBreadboard().getResistor()
						.get(componentTrack[0][1]).getPosition()[0][0] != -1) {
					brPanel.getBreadboard()
							.setComponent(
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][1],
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][0], null);
					brPanel.getBreadboard()
							.setComponent(
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][1],
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][0] + 1, null);
					brPanel.getBreadboard()
							.setComponent(
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][1],
									brPanel.getBreadboard().getResistor()
											.get(componentTrack[0][1])
											.getPosition()[0][0] + 2, null);
				}
			}
			
			//Update the resistors position integers.
			brPanel.getBreadboard().getResistor().get(componentTrack[0][1])
					.setPosition(0, 0, x);
			brPanel.getBreadboard().getResistor().get(componentTrack[0][1])
					.setPosition(0, 1, y);
		} else if (componentTrack[0][0] == 2) {
			tempLoc = new Point(
					(brPanel.getBreadboard().getSwitch()
							.get(componentTrack[0][1]).getLocationOnScreen().x - parentPos.x) - 30,
					(brPanel.getBreadboard().getSwitch()
							.get(componentTrack[0][1]).getLocationOnScreen().y - parentPos.y) - 97);

			// Restricts location to within breadboard.
			if (tempLoc.x < 0) {
				tempLoc.x = 0;
			} else if (tempLoc.x > 637) {
				tempLoc.x = 637;
			}
			if (tempLoc.y < 0) {
				tempLoc.y = 0;
			} else if (tempLoc.y > 287) {
				tempLoc.y = 287;
			}

			// Snap to closest grid point.
			x = (int) tempLoc.x - (tempLoc.x % 10) + 37;
			y = (int) tempLoc.y - (tempLoc.y % 20) + 100;

			// Offset y or location of resistor based on its location.
			// Necessary because the breadboard is not a consistent grid.
			if (tempLoc.y < 42) {
				y += 7;
			} else if (tempLoc.y < 256) {
				tempLoc.y -= 57;
				y = (int) tempLoc.y - (tempLoc.y % 20) + 157;
			} else {
				y += 7;
			}

			// Update bounds of the resistor based on calculated x and y values.
			brPanel.getBreadboard().getSwitch().get(componentTrack[0][1])
					.setBounds(x, y, 27, 27);

			// Calculate for the y and x values in the component map.
			if (y == 100 || y == 120 || y == 360 || y == 380) {
				y = (y - 100) / 20;
			} else {
				y = ((y - 150) / 20) + 2;
			}
			x = (x - 32) / 10;

			// Update the values in the component map to match the component
			// positions on the breadboard.
			brPanel.getBreadboard().setComponent(
					y,
					x,
					brPanel.getBreadboard().getSwitch()
							.get(componentTrack[0][1]));
			if (x < 63) {
				brPanel.getBreadboard().setComponent(
						y,
						x + 1,
						brPanel.getBreadboard().getSwitch()
								.get(componentTrack[0][1]));
			}
			// If the component was previously on the map, remove its old entry.
			if (brPanel.getBreadboard().getSwitch().get(componentTrack[0][1])
					.getPosition()[0][0] != -1) {
				brPanel.getBreadboard().setComponent(
						brPanel.getBreadboard().getSwitch()
								.get(componentTrack[0][1]).getPosition()[0][1],
						brPanel.getBreadboard().getSwitch()
								.get(componentTrack[0][1]).getPosition()[0][0],
						null);
				brPanel.getBreadboard()
						.setComponent(
								brPanel.getBreadboard().getSwitch()
										.get(componentTrack[0][1])
										.getPosition()[0][1] + 1,
								brPanel.getBreadboard().getSwitch()
										.get(componentTrack[0][1])
										.getPosition()[0][0], null);
			}
		}
	}

	@Override
	public void mouseMoved(MouseEvent e) {
	}

	@Override
	public void mouseClicked(MouseEvent e) {

	}

	@Override
	public void mouseEntered(MouseEvent e) {

	}

	@Override
	public void mouseExited(MouseEvent e) {
	}

}
